<?php
namespace plugin\entity_manager\src;

/**
 * The MIT License
 *
 * Copyright (c) <year> <copyright holders>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @author Damiano Ciarla
 * @package plugin
 * @subpackage entity_manager/class
 */
class EntityManager
{
	/**
	 * Istanza della classe di configurazione dell'EntityManager
	 *
	 * @var array|config\EntityManagerConfig
	 */
	private static $config = array();
	
	/**
	 * Nome della configurazione di default
	 *
	 * @var string
	 */
	private static $defaultConfigurationName;

	/**
	 * Metodo che restituisce l'istanza della classe di confiugrazione del plugin
	 *
	 * @param string $connectionName Eventuale nome della connessione
	 *
	 * @return config\EntityManagerConfig
	 */
	public static function getConfig($connectionName = null)
	{
		if($connectionName !== null && !array_key_exists($connectionName, self::$config))
			throw new Exception;
		
		return self::$config[(($connectionName === null) ? self::$defaultConfigurationName : $connectionName)];
	}
	
	/**
	 * Metodo che aggiunge una nuova connessione all'entitymanager con il quale effettuare le
	 * operazioni di reperiento e inserimento dati
	 *
	 * @param config\EntityManagerConfig $config Istanza della classe di configurazione
	 * @param type $isDefault Parametro booleano che imposta se questa connessione deve essere utilizzata come predefinita
	 */
	public static function addConfig(config\EntityManagerConfig $config, $isDefault = false)
	{
		self::$config[$config->getDbConfig()->connectionName] = $config;
		
		if($isDefault || (!$isDefault && self::$defaultConfigurationName === null))
		{
			self::$defaultConfigurationName = $config->getDbConfig()->connectionName;
			\bxd\lib\db\Db::getInstance()->connect($config->getDbConfig()->connectionName, true);
		}
	}

	/**
	 * Metodo che restituisce un istanza della classe "query\Find"
	 * con la quale eseguire le operazioni di ricerca dei dati
	 *
	 * @param AbstractEntity $entity Istanza di una classe Entity
	 *
	 * @return query\Find
	 */
	public static function find(AbstractEntity $entity)
	{
		return new query\Find(&$entity);
	}

	/**
	 * Metodo che restituisce un istanza della classe "query\Find"
	 * con la quale eseguire le operazioni di ricerca dei dati
	 *
	 * @param AbstractEntity $entity Istanza di una classe Entity
	 *
	 * @return query\Find
	 */
	public static function findOne(AbstractEntity $entity)
	{
		return new query\Find(&$entity, true);
	}

	/**
	 * Metodo che restituisce un istanza della classe "query\Count"
	 * con la quale eseguire il conteggio dei dati
	 *
	 * @param AbstractEntity $entity Istanza di una classe Entity
	 *
	 * @return query\Count
	 */
	public static function count(AbstractEntity $entity)
	{
		return new query\Count(&$entity);
	}

	/**
	 * Metodo che restituisce un istanza della classe "query\Delete"
	 * con la quale è possibile seguire la cencellazione dei record all'interno della tabella
	 * specificata nella entity
	 *
	 * @param AbstractEntity $entity Istanza di una classe Entity
	 *
	 * @return query\Delete
	 */
	public static function delete(AbstractEntity $entity)
	{
		return new query\Delete(&$entity);
	}

	/**
	 * Metodo che restituisce un istanza della classe "query\Update"
	 * con la quale è possibile seguire la modifica dei record all'interno della tabella
	 * specificata nella entity
	 *
	 * @param AbstractEntity $entity Istanza di una classe Entity
	 *
	 * @return query\Update
	 */
	public static function update(AbstractEntity $entity)
	{
		return new query\Update(&$entity);
	}

	/**
	 * Metodo che restituisce un istanza della classe "query\Insert"
	 * con la quale è possibile seguire l'inserimento dei record all'interno della tabella
	 * specificata nella entity
	 *
	 * @param AbstractEntity $entity Istanza di una classe Entity
	 *
	 * @return query\Insert
	 */
	public static function insert(AbstractEntity $entity)
	{
		return new query\Insert(&$entity);
	}

	/**
	 * Metodo che si occupa della generazione delle entity e restituisce un array
	 * con il dettaglio dell'operazione
	 *
	 * @return array
	 */
	public static function generateEntity($connectionName = null)
	{
		$folder = self::getConfig($connectionName)->folderModel();

		// Costruisco il path dove sono contenuti i model
		$basePath = $pathBase = \Bxd::getInstance()->pathApplication . '/' . $folder;

		// Verifico se la cartella esiste
		if(!is_dir($basePath))
			throw new exception\EntityManagerException($folder, exception\EntityManagerException::FOLDER_MODEL_NOT_EXISTS);

		// Verifico se la cartella è scrivibile
		if(!is_writable($basePath))
			throw new exception\EntityManagerException($folder, exception\EntityManagerException::FOLDER_MODEL_IS_NOT_WRITABLE);

		// Verifico se la cartella base è presente altrimenti la creo
		if(!is_dir($basePath . '/base'))
			mkdir($basePath . '/base', 0777, true);

		$db = self::getConfig($connectionName)->getConnection();

		// Recupero la lista delle tabelle
		$tables = $db->execute('SHOW TABLES')->fetchAll(\PDO::FETCH_COLUMN);

		$listGeneratingEntity = array(
			'generated' => array(),
			'removed' => array()
		);

		// Ciclo ogni tabella e costruisco i file per le entity
		foreach($tables as $table)
		{
			$fileBase  = "<?php\n";
			$fileBase .= "namespace entity_manager\\model\\base;\n\n";
			$fileBase .= "/**\n";
			$fileBase .= " * @TABLE \"{$table}\"\n";

			// Ricerca delle relazioni
			$sql = "SELECT REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME, TABLE_NAME, COLUMN_NAME
					FROM information_schema.KEY_COLUMN_USAGE
					WHERE (REFERENCED_TABLE_NAME = ? OR TABLE_NAME = ?)
					AND REFERENCED_TABLE_NAME IS NOT NULL
					AND TABLE_NAME IS NOT NULL
					AND TABLE_SCHEMA = ?";

			$relationships = $db->execute($sql, array($table, $table, $db->getConnection(true)->dbname))->fetchAll(\PDO::FETCH_ASSOC);

			// Analizzo ogni relazione trovata
			foreach($relationships as $relationship)
			{
				// Esiste la relazione "UNO A MOLTI" verso un'altra tabella
				if($relationship['REFERENCED_TABLE_NAME'] == $table)
				{
					$className = str_replace(" ", "", ucwords(str_replace("_", " ", $relationship['TABLE_NAME'])));

					$fileBase .= " * @JOIN_TO \"{$relationship['REFERENCED_COLUMN_NAME']} > {$relationship['TABLE_NAME']}.{$relationship['COLUMN_NAME']}\"\n";
					$fileBase .= " * @GET \"{$className}|{$relationship['TABLE_NAME']} > {$relationship['REFERENCED_COLUMN_NAME']}|{$relationship['COLUMN_NAME']}\"\n";
				}

				// Esiste la relazione "MOLTI A UNO" proveniente da un'altra tabella
				if($relationship['TABLE_NAME'] == $table)
				{
					$className = str_replace(" ", "", ucwords(str_replace("_", " ", $relationship['REFERENCED_TABLE_NAME'])));

					$fileBase .= " * @JOIN_FROM \"{$relationship['COLUMN_NAME']} < {$relationship['REFERENCED_TABLE_NAME']}.{$relationship['REFERENCED_COLUMN_NAME']}\"\n";
					$fileBase .= " * @GET \"{$className}|{$relationship['REFERENCED_TABLE_NAME']} > {$relationship['COLUMN_NAME']}|{$relationship['REFERENCED_COLUMN_NAME']}\"\n";
				}

			}

			// Creo il nome della classe togliendo eventuali "_" e mettendo ogni parte della stringa Cammellata
			$className = str_replace(" ", "", ucwords(str_replace("_", " ", $table)));

			$fileBase .= " */\n";

			$listGeneratingEntity['generated'][$className]['base'] = 'created';
			$listGeneratingEntity['generated'][$className]['extended'] = 'already exists';

			$fileBase .= "class Base{$className} extends \\plugin\\entity_manager\\src\\AbstractEntity\n";
			$fileBase .= "{";

			// Recupero le informazioni sui campi della tabella
			$info = $db->execute("DESCRIBE `{$table}`")->fetchAll(\PDO::FETCH_ASSOC);

			foreach($info as $field)
			{
				// Recupero un'eventuale lunghezza del campo
				$findLength = preg_match('/\((.*)\)/', $field['Type'], $lenght);

				// In base alla presenza o meno della lunghezza del campo recupero la tipologia del campo
				preg_match((($findLength) ? '/(.*)\(/' : '/(.*)/'), $field['Type'], $type);

				$fileBase .= "\n\t/**\n\t";

				// Imposto la chiave primaria se presente
				if($field['Key'] == "PRI")
					$fileBase .= " * @PRIMARY\n\t";

				// Imposto nome del campo e ripologia
				$fileBase .= " * @COLUMN \"{$field['Field']}\"\n\t";
				$fileBase .= " * @TYPE \"{$type[1]}\"\n\t";

				// Imposto la lunghezza se presente
				if($findLength)
					$fileBase .= " * @LENGTH \"{$lenght[1]}\"\n\t";

				// Imposto il campo NOT_NULL se le specifiche della tabella lo richiedono
				if($field['Null'] == "NO")
					$fileBase .= " * @NOT_NULL\n\t";

				// Imposto il valore AUTO_INCREMENT se presente
				if($field['Extra'] == "auto_increment")
					$fileBase .= " * @AUTO_INCREMENT\n\t";

				// Imposto il valore di DEFAULT se presente
				if($field['Default'] != null)
					$fileBase .= " * @DEFAULT \"{$field['Default']}\"\n\t";

				$fileBase .= " */\n\t";

				// Creo la variabile pubblica
				$fileBase .= "public \${$field['Field']};\n";
			}

			$fileBase .= "}\n";
			$fileBase .= "?>";

			// Eseguo la scrittura del file
			$fh = fopen($basePath . '/base/Base' . $className . '.php', 'w');
			fwrite($fh, $fileBase);
			fclose($fh);

			// Verifico se il file che l'utente può estendere è presente o meno. Se non presente
			// creo il file senza alcun metodo altrimenti lo lascio inalterato
			if(!file_exists($basePath . '/' . $className . '.php'))
			{
				$fileExtended  = "<?php\n";
				$fileExtended .= "namespace entity_manager\\model;\n\n";
				$fileExtended .= "class {$className} extends base\\Base{$className}\n";
				$fileExtended .= "{\n\t";
				$fileExtended .= "/* CUSTOM METHOD */\n";
				$fileExtended .= "}\n";
				$fileExtended .= "?>";

				$fh = fopen($basePath . '/' . $className . '.php', 'w');
				fwrite($fh, $fileExtended);
				fclose($fh);

				$listGeneratingEntity['generated'][$className]['extended'] = 'created';
			}
		}

		// Verifico la presenza di "entity base" obsolete
		foreach(scandir($basePath . '/base') as $file)
		{
			$className = substr($file, 0, strpos($file, "."));
			$className = ((strlen($className) > 1) ? substr($className, 4) : $className);

			if($file[0] != "." && $className != "" && !array_key_exists($className, $listGeneratingEntity['generated']))
			{
				unlink($basePath . '/base/' . $file);
				$listGeneratingEntity['removed'][$className]['base'] = 'removed';
			}
		}

		// Verifico la presenza di "entity estese" obsolete
		foreach(scandir($basePath) as $file)
		{
			$className = substr($file, 0, strpos($file, "."));

			if($file[0] != "." && $className != "" && !array_key_exists($className, $listGeneratingEntity['generated']))
			{
				unlink($basePath . '/' . $file);
				$listGeneratingEntity['removed'][$className]['extended'] = 'removed';
			}
		}

		return $listGeneratingEntity;
	}
}
?>
